﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms.DataVisualization.Charting;
using System.Drawing;
using System.ComponentModel;

namespace MoneyWentWhere.Extensions
{

    #region ChartExt
    public static class ChartExt
    {
        public static Chart CreateChart<T>(this IEnumerable<T> data,
            String title = null,
            SeriesChartType chartType = SeriesChartType.Bar)
        {
            if (data == null) { throw new ArgumentNullException("data is null"); }
            if (!data.Any()) { throw new ArgumentException("data is empty"); }

            // TODO: show nice big chart title (in addition to panel title)

            // If type in the collection is not specific, get the runtime type
            Type itemType = typeof(T);
            if (itemType == typeof(object))
            {
                itemType = data.First().GetType();
            }

            if (title == null &&
                !itemType.Name.StartsWith("<>f__AnonymousType"))
            {
                title = itemType.Name + " Chart";
            }

            var area = new ChartArea();
            var legend = new Legend("Legend");

            var c = new Chart();
            c.ChartAreas.Add(area);
            c.Legends.Add(legend);

            if (title != null)
            {
                Title t = new Title(title);
                t.Font = new Font(t.Font.FontFamily, 15);
                c.Titles.Add(t);
            }

            if (IsNumericType(itemType))
            {
                // Enumerable of simple numeric values
                // No legend, no axis labels
                var s = new Series();
                s.ChartType = chartType;

                foreach (T item in data)
                {
                    double val = Convert.ToDouble(item);
                    s.Points.Add(val);
                }

                c.Series.Add(s);
            }
            else
            {
                // Enumerable of complex objects

                // Read axis labels
                List<string> axisLabels = GetAxisLabels(data, itemType);
                if (axisLabels != null)
                {
                    var axis = new Axis();
                    for (int i = 0; i < axisLabels.Count; i++)
                    {
                        axis.CustomLabels.Add(i + .5, i + 1.5, axisLabels[i]);
                    }
                    area.AxisX = axis;
                }

                // Get properties, add data points for each
                var props = GetNumericPropertiesAndFields(itemType);
                if (!props.Any())
                {
                    throw new ArgumentException("Type " + itemType + " has no numeric properties.");
                }

                if (chartType == SeriesChartType.Pie)
                {
                    props = new MemberInfo[] { props[0] };
                }

                foreach (var prop in props)
                {
                    var s = new Series(prop.Name);
                    s.Legend = "Legend";
                    s.ChartType = chartType;
                    //s.IsValueShownAsLabel

                    // Add data
                    PropertyInfo pi = prop as PropertyInfo;
                    FieldInfo fi = prop as FieldInfo;

                    int index = 0;
                    foreach (T item in data)
                    {
                        object objVal = GetPropertyValue(prop, item);
                        double val = Convert.ToDouble(objVal);

                        s.Points.Add(val);

                        // Labels for pie charts
                        if (axisLabels != null && chartType == SeriesChartType.Pie)
                        {
                            s.Points[index].Label = axisLabels[index];
                        }

                        index++;
                    }
                    c.Series.Add(s);
                }
            }

            return c;
        }

        static List<String> GetAxisLabels<T>(IEnumerable<T> data, Type itemType)
        {
            var axisLabelProp = GetAxisLabelProperty(itemType);
            if (axisLabelProp == null) { return null; }

            return data
                .Select(x => Convert.ToString(GetPropertyValue(axisLabelProp, x)))
                .ToList();
        }

        static object GetPropertyValue(MemberInfo mi, object item)
        {
            PropertyInfo pi = mi as PropertyInfo;
            FieldInfo fi = mi as FieldInfo;

            if (pi != null) { return pi.GetValue(item); }
            if (fi != null) { return fi.GetValue(item); }
            return null;
        }


        // Get data series from the object
        static MemberInfo[] GetNumericPropertiesAndFields(Type type)
        {
            var members = type.GetMembers()
                .Where(x => IsNumericType(GetReturnType(x)));

            return members.ToArray();
        }

        static bool IsNumericType(Type t)
        {
            return t == typeof(int) || t == typeof(long) || t == typeof(short) || t == typeof(byte) ||
                   t == typeof(double) || t == typeof(float) || t == typeof(decimal);
        }

        static MemberInfo GetAxisLabelProperty(Type type)
        {
            return type.GetMembers()
                .Where(x => IsAxisLabelType(GetReturnType(x)))
                .FirstOrDefault();
        }

        static bool IsAxisLabelType(Type t)
        {
            return t == typeof(string) || t == typeof(DateTime);
        }

        // Get return type of property or field, null if none
        static Type GetReturnType(MemberInfo mi)
        {
            PropertyInfo pi = mi as PropertyInfo;
            FieldInfo fi = mi as FieldInfo;

            if (pi != null) { return pi.PropertyType; }
            else if (fi != null) { return fi.FieldType; }
            return null;
        }
    }
    #endregion


}
